"""The cleaning functions"""

import shutil
import os
import readers
import re
import winshell
import subprocess

DEFAULT_SONG_PROFILE='data\\songProfile.json'

def backupFile(fil):
    shutil.copyfile(fil, '%s_backup%s'%(os.path.splitext(fil)[0],
                                        os.path.splitext(fil)[1]))

def cleanSongs(songProfileFile=DEFAULT_SONG_PROFILE):
    """Cleans up an entire song file"""
    backupFile(readers.loadFromJson(songProfileFile)['elements'])
    profile=readers.loadProfile(songProfileFile)
    profile.elements=completeClean(profile.elements, profile.uniqueAtts)
    profile.save()

def completeClean(elements, uniqueAtts):
    """Applies all of the cleaning functions to a set of elements"""
    elements=sanitizeAttributes(elements,
                                 ['Name', 'Artist', 'Album', 'Genre',
                                  'Track Number', 'Location', 'Rating'])
    elements=mergeSongs(elements, lambda ele: [ele[att] for att in uniqueAtts])
    elements=removeDuplicates(elements)
    elements=unfeature(elements)
    elements=flattenRating(elements)
    elements=flattenLocation(elements)
    return elements

def removeDuplicates(profileElements):
    """Removes exact duplicates"""
    elements=[]
    for element in profileElements:
        if element not in elements:
            elements.append(element)
    return elements

def sanitizeAttributes(elements, atts):
    """Sets all of the elements to only contain a certain set of attributes
    and have a value for all of them"""
    return [dict((att,element[att] if att in element else '') for att in atts)
            for element in elements]

def mergeSongs(elements, identifyFunction):
    """Returns the set of elements after merging together the given set
    using the identify function to determine which are the same
    Assumes that they are sanitized for attributes"""
    duplicates=[]
    merged=[]
    for element in elements:
        iDups=[ele for ele in elements if identifyFunction(ele)==identifyFunction(element)]
        if len(iDups)>1:
            duplicates.append(iDups)
        else:
            merged.append(element) #No need to merge, so directly appending
    if not duplicates:
        return elements
    else:
        for dup in duplicates:
            mergedEle=dict((att, set(ele[att] for ele in dup))
                           for att in dup[0])
            for att in mergedEle:
                if len(mergedEle[att])==1:
                    mergedEle[att]=mergedEle[att].pop()
                else:
                    mergedEle[att]=list(mergedEle[att])
            merged.append(mergedEle)
    return merged

def unfeature(elements):
    """Moves featured artists from the name to the artists"""
    FEATUREWORD='[fF](ea)?t\.?'
    bracketedFeatures=re.compile('\(%s.+?\)'%(FEATUREWORD))
    dashedFeatures=re.compile('- %s.+'%(FEATUREWORD))
    for element in elements:
        res=False
        bracketedp=bracketedFeatures.search(element['Name'])
        if bracketedp:
            grp=bracketedp.group()
            res=grp[grp.index(' ')+1:-1]
        dashedp=dashedFeatures.search(element['Name'])
        if dashedp:
            grp=dashedp.group()
            res=' '.join(part for part in grp.split()[2:])
        if res:
            element['Name']=element['Name'].replace(grp, '').strip()
            res=[part.title() for part in res.split(',')]
            for extra in ('&', 'And'):
                if extra in res[-1]:
                    toSplit=res[-1]
                    res=res[:-1]
                    res.extend(toSplit.split(extra))
            element['Artist']=[element['Artist']]
            for part in res:
                element['Artist'].append(part.strip())
    return elements

def flattenRating(elements):
    """Flattens the rating down to the maximum"""
    for element in elements:
        if type(element['Rating'])==list:
            element['Rating']=max(element['Rating'])
    return elements

def flattenLocation(elements):
    """Flattens the location and removes all extra copies of the file"""
    for element in elements:
        if type(element['Location'])==list:
            try:
                element['Location'].remove('invalid')
            except ValueError:
                pass
            for fil in element['Location'][1:]:
                try:
                    os.remove(fil)
                except WindowsError:
                    pass
            element['Location']=element['Location'][0]
    return elements


def bonkConvertSongs(elements, bonkPath):
    """Converts all songs to mp3 for song playing using bonk"""
    for element in elements:
        ext=os.path.splitext(element['Location'])[1]
        if ext!='.mp3':
            dest=element['Location'].replace(ext, '.mp3')
            cmd='"%s" -o "%s" "%s"'%(bonkPath, dest, element['Location'])
            result=subprocess.call(cmd)
            if result==0:
                winshell.delete_file(element['Location'], no_confirm=True,
                                     silent=True)
                element['Location']=dest
            else:
                print 'could not convert', element['Location']
    return elements

def oneCallBonk(songProfileFile=DEFAULT_SONG_PROFILE,
                bonkPath='D:\\Apps\\BonkEnc\\BEcmd.exe'):
    """One call to convert all songs"""
    profile=readers.loadProfile(songProfileFile) #Make this a with
    profile.elements=bonkConvertSongs(profile.elements, bonkPath)
    profile.save()

class RestructureFiles(object):
    """Handles the restructuring of the files into the correct directories"""
    #For now, writing this specifically for songs. Must generalize the pattern, and then can use for comics or videos as well
    def __init__(self, profile):
        self.__baseDir=profile.baseDir
        self.__elements=profile.elements
    def __call__(self):
        for element in self.__elements:
            self.__moveFile(element)
    @staticmethod
    def __cleanForFileName(element, att):
        """Removes characters which cannot be part of the file name"""
        try:
            if isinstance(element[att], list):
                name=str(element[att][0])
            else:
                name=str(element[att])
        except (KeyError, UnicodeEncodeError):
            return ''
        for ch in r'\/*?|<>:"':
            name=name.replace(ch, '')
        return name.strip()
    def __moveFile(self, element):
        """Moves the element into the correct place"""
        if element['Location']=='invalid':
            return
        folder=self.__baseDir
        if not os.path.isdir(folder):
            os.mkdir(folder)
        for subFolder in (self.__cleanForFileName(element, att)
                         for att in ('Genre', 'Artist', 'Album')):
            folder+='/'+subFolder
            if not os.path.isdir(folder):
                os.mkdir(folder)
        atts=['Artist', 'Album']
        if 'Track Number' in element:
            atts.append('Track Number')
        atts.append('Name')
        name=' - '.join(self.__cleanForFileName(element, att)
                        for att in atts)
        location='%s/%s%s'%(folder, name,
                            os.path.splitext(element['Location'])[1].lower())
        if element['Location']==location:
            return
        try:
            shutil.move(element['Location'], location)
        except IOError:
            print 'failed'
            return
        oldFolder=os.path.split(element['Location'])[0]
        if not os.listdir(oldFolder):
            shutil.rmtree(oldFolder)
        element['Location']=location
        
